使用 semaphore、 Mutex 可以達到對 BlockQueue 的要求。單純使用 Lock ,能確保共用資源操作的原子性,卻無法達到通知生產者或消費者的目的。
所以接下來,來使用 .Net 之中,可以使用基於 EventWaitHandle 的 ManualResetEvent或ManualResetEventSlim,達到通知 BlockQueue 的特性。
可能會有人有疑問,AutoResetEvent 一樣是繼承 EventWaitHandle ,為何選擇 ManualResetEvent ,而不使用AutoResetEvent ?
| ManualResetEvent | AutoResetEvent | 
|---|---|
| 若鎖定後,要再次使用前,必須先重置狀態。 | 若鎖定後,使用時,會自行重置狀態。 | 
| 一次可以釋放所有鎖定的執行緒。 | 一次只能釋放一個執行緒。 | 
而在 BlockQueue 之中,除非特別去記錄執行緒資訊,不然無法明確得知執行緒的數量。使用ManualRestEvent可以一口氣釋放所有的執行緒,減少一筆筆釋放的麻煩。
同時,在 .Net Core 之中,還有一個 ManualRestEventSlim的類別提供使用,它可視為輕量化的 ManualRestEvent 。
據官方的說法,ManualResetEventSlim的執行效能高於 ManualRestEvent,但是兩者間還是有些差異。
ManualResetEventSlim 只能在同一個 Process 範圍內作用;ManualResetEvent則沒有這個限制。ManualResetEventSlim 的短時間的鎖定時,所花費的成本較低。public class BlockQueue<T> : LockQueue<T>
{
    private ManualResetEventSlim _enqueueWait;
    private ManualResetEventSlim _dequeueWait;
    public BlockQueue()
    {
        _enqueueWait = new ManualResetEventSlim(false);
        _dequeueWait = new ManualResetEventSlim(false);
    }
    public void Enqueue(T item)
    {
        while (true)
        {
        	// 因為 LockQueue 的上限是 20, 偷懶寫法。
            if (this.Count == 20)
            {
                _enqueueWait.Wait();
            }
            base.Enqueue(item);
            //	準備 Enqueue 下次的 Wait 
            _enqueueWait.Reset();
            
            //	Dequeue 放行
            _dequeueWait.Set();
        }
    }
    public T Dequeue()
    {
        while (true)
        {
            if (base.IsEmpty == true)
            {
                _dequeueWait.Wait();
            }
            var dequeue = base.Dequeue();
            _dequeueWait.Reset();
            _enqueueWait.Set();
            return dequeue;
        }
    }
}
測試程式,請見 Lock 章節。
不管是 Lock、Semaphore、Mutex、ManualReset,提到的都概念與簡單的實作練習。在 .Net Core 之中,己經針對生產者消費者模型,設計 IProducerConsumerCollection 介面。
像 BlockCollection 或  ConcurrentQueue 均為IProducerConsumerCollection的實作,提供安全執行緒集合適用的封鎖和界限容量,
也因為 .Net core 之中,己經提供 System.Collections.Concurrent.ConcurrentQueue 這個更加完整的 Queue ,在往後的實作,改為使用 ConcurrentQueue 。